{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "https://langchain-ai.github.io/langgraph/how-tos/tool-calling-errors/#using-the-prebuilt-toolnode\n",
    "\n",
    "## Custom strategies\n",
    "自定义策略"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "463d126b2b6d4435"
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "HUMAN: Write me an incredible haiku about water.\n",
      "\n",
      "AI: \n"
     ]
    }
   ],
   "source": [
    "from langgraph.constants import END, START\n",
    "from langgraph.graph import MessagesState, StateGraph\n",
    "from langgraph.prebuilt import ToolNode\n",
    "from langchain_core.tools import tool\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from pydantic import BaseModel, Field\n",
    "import os\n",
    "from langchain_openai import ChatOpenAI\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "load_dotenv()\n",
    "\n",
    "\n",
    "class HaikuRequest(BaseModel):\n",
    "    topic: list[str] = Field(\n",
    "        max_length=3,\n",
    "        min_length=3,\n",
    "    )\n",
    "\n",
    "\n",
    "@tool\n",
    "def master_haiku_generator(request: HaikuRequest):\n",
    "    \"\"\"Generates a haiku based on the provided topics.\"\"\"\n",
    "    model = ChatOpenAI(\n",
    "        # 若没有配置环境变量，请用百炼API Key将下行替换为：api_key=\"sk-xxx\",\n",
    "        openai_api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "        openai_api_base=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    "        model_name=\"qwen-max\",\n",
    "        temperature=0, streaming=True,\n",
    "    )\n",
    "    chain = model | StrOutputParser()\n",
    "    topics = \", \".join(request.topic)\n",
    "    haiku = chain.invoke(f\"Write a haiku about {topics}\")\n",
    "    return haiku\n",
    "\n",
    "\n",
    "tool_node = ToolNode([master_haiku_generator])\n",
    "model_with_tools = ChatOpenAI(\n",
    "    # 若没有配置环境变量，请用百炼API Key将下行替换为：api_key=\"sk-xxx\",\n",
    "    openai_api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "    openai_api_base=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    "    model_name=\"qwen-max\",\n",
    "    temperature=0, streaming=True,\n",
    ").bind_tools([master_haiku_generator])\n",
    "\n",
    "def should_continue(state: MessagesState):\n",
    "    messages = state[\"messages\"]\n",
    "    last_message = messages[-1]\n",
    "    if last_message.tool_calls:\n",
    "        return \"tools\"\n",
    "    return END\n",
    "\n",
    "\n",
    "def call_model(state: MessagesState):\n",
    "    messages = state[\"messages\"]\n",
    "    response = model_with_tools.invoke(messages)\n",
    "    return {\"messages\": [response]}\n",
    "\n",
    "workflow = StateGraph(MessagesState)\n",
    "# Define the two nodes we will cycle between\n",
    "workflow.add_node(\"agent\", call_model)\n",
    "workflow.add_node(\"tools\", tool_node)\n",
    "\n",
    "workflow.add_edge(START, \"agent\")\n",
    "workflow.add_conditional_edges(\"agent\", should_continue, [\"tools\", END])\n",
    "workflow.add_edge(\"tools\", \"agent\")\n",
    "\n",
    "app = workflow.compile()\n",
    "\n",
    "response = app.invoke(\n",
    "    {\"messages\": [(\"human\", \"Write me an incredible haiku about water.\")]},\n",
    "    {\"recursion_limit\": 10},\n",
    ")\n",
    "\n",
    "for message in response[\"messages\"]:\n",
    "    string_representation = f\"{message.type.upper()}: {message.content}\\n\"\n",
    "    print(string_representation)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-11-05T05:59:45.165114Z",
     "start_time": "2024-11-05T05:59:41.878304Z"
    }
   },
   "id": "851c491795687ad3",
   "execution_count": 2
  },
  {
   "cell_type": "markdown",
   "source": [
    "我们可以看到，模型尝试两次才得到正确的输入。一个更好的策略可能是，在失败时去掉一些内容以减少干扰，然后调用更先进的模型。以下是一个例子。我们还使用一个自定义节点来调用工具，而不是预构建的 ToolNode："
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "341691abaf0b287c"
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "data": {
      "image/jpeg": "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",
      "text/plain": "<IPython.core.display.Image object>"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.core.display import Image\n",
    "from typing import Literal\n",
    "import json\n",
    "\n",
    "from langchain_core.messages import AIMessage, ToolMessage\n",
    "from langchain_core.messages.modifier import RemoveMessage\n",
    "\n",
    "\n",
    "@tool\n",
    "def master_haiku_generator(request: HaikuRequest):\n",
    "    \"\"\"Generates a haiku based on the provided topics.\"\"\"\n",
    "    model = ChatOpenAI(\n",
    "        # 若没有配置环境变量，请用百炼API Key将下行替换为：api_key=\"sk-xxx\",\n",
    "        openai_api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "        openai_api_base=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    "        model_name=\"qwen-max\",\n",
    "        temperature=0, streaming=True,\n",
    "    )\n",
    "    chain = model | StrOutputParser()\n",
    "    topics = \", \".join(request.topic)\n",
    "    haiku = chain.invoke(f\"Write a haiku about {topics}\")\n",
    "    return haiku\n",
    "\n",
    "def call_tool(state: MessagesState):\n",
    "    tools_by_name = {master_haiku_generator.name: master_haiku_generator}\n",
    "    messages = state[\"messages\"]\n",
    "    last_message = messages[-1]\n",
    "    output_messages = []\n",
    "    for tool_call in last_message.tool_calls:\n",
    "        try:\n",
    "            tool_result = tools_by_name[tool_call[\"name\"]].invoke(tool_call[\"args\"])\n",
    "            output_messages.append(\n",
    "                ToolMessage(\n",
    "                    content=json.dumps(tool_result),\n",
    "                    name=tool_call[\"name\"],\n",
    "                    tool_call_id=tool_call[\"id\"],\n",
    "                )\n",
    "            )\n",
    "        except Exception as e:\n",
    "            # Return the error if the tool call fails\n",
    "            output_messages.append(\n",
    "                ToolMessage(\n",
    "                    content=\"\",\n",
    "                    name=tool_call[\"name\"],\n",
    "                    tool_call_id=tool_call[\"id\"],\n",
    "                    additional_kwargs={\"error\": e},\n",
    "                )\n",
    "            )\n",
    "    return {\"messages\": output_messages}\n",
    "\n",
    "model = ChatOpenAI(\n",
    "        # 若没有配置环境变量，请用百炼API Key将下行替换为：api_key=\"sk-xxx\",\n",
    "        openai_api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "        openai_api_base=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    "        model_name=\"qwen-max\",\n",
    "        temperature=0, streaming=True,\n",
    "    )\n",
    "model_with_tools = model.bind_tools([master_haiku_generator])\n",
    "\n",
    "better_model = ChatOpenAI(\n",
    "        # 若没有配置环境变量，请用百炼API Key将下行替换为：api_key=\"sk-xxx\",\n",
    "        openai_api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "        openai_api_base=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    "        model_name=\"qwen-max\",\n",
    "        temperature=0, streaming=True,\n",
    "    )\n",
    "better_model_with_tools = better_model.bind_tools([master_haiku_generator])\n",
    "\n",
    "def should_continue(state: MessagesState):\n",
    "    messages = state[\"messages\"]\n",
    "    last_message = messages[-1]\n",
    "    if last_message.tool_calls:\n",
    "        return \"tools\"\n",
    "    return END\n",
    "\n",
    "\n",
    "def should_fallback(\n",
    "    state: MessagesState,\n",
    ") -> Literal[\"agent\", \"remove_failed_tool_call_attempt\"]:\n",
    "    messages = state[\"messages\"]\n",
    "    failed_tool_messages = [\n",
    "        msg\n",
    "        for msg in messages\n",
    "        if isinstance(msg, ToolMessage)\n",
    "        and msg.additional_kwargs.get(\"error\") is not None\n",
    "    ]\n",
    "    if failed_tool_messages:\n",
    "        return \"remove_failed_tool_call_attempt\"\n",
    "    return \"agent\"\n",
    "\n",
    "def call_model(state: MessagesState):\n",
    "    messages = state[\"messages\"]\n",
    "    response = model_with_tools.invoke(messages)\n",
    "    return {\"messages\": [response]}\n",
    "\n",
    "\n",
    "def remove_failed_tool_call_attempt(state: MessagesState):\n",
    "    messages = state[\"messages\"]\n",
    "    # Remove all messages from the most recent\n",
    "    # instance of AIMessage onwards.\n",
    "    last_ai_message_index = next(\n",
    "        i\n",
    "        for i, msg in reversed(list(enumerate(messages)))\n",
    "        if isinstance(msg, AIMessage)\n",
    "    )\n",
    "    messages_to_remove = messages[last_ai_message_index:]\n",
    "    return {\"messages\": [RemoveMessage(id=m.id) for m in messages_to_remove]}\n",
    "\n",
    "# Fallback to a better model if a tool call fails\n",
    "def call_fallback_model(state: MessagesState):\n",
    "    messages = state[\"messages\"]\n",
    "    response = better_model_with_tools.invoke(messages)\n",
    "    return {\"messages\": [response]}\n",
    "\n",
    "\n",
    "workflow = StateGraph(MessagesState)\n",
    "\n",
    "workflow.add_node(\"agent\", call_model)\n",
    "workflow.add_node(\"tools\", call_tool)\n",
    "workflow.add_node(\"remove_failed_tool_call_attempt\", remove_failed_tool_call_attempt)\n",
    "workflow.add_node(\"fallback_agent\", call_fallback_model)\n",
    "\n",
    "workflow.add_edge(START, \"agent\")\n",
    "workflow.add_conditional_edges(\"agent\", should_continue, [\"tools\", END])\n",
    "workflow.add_conditional_edges(\"tools\", should_fallback)\n",
    "workflow.add_edge(\"remove_failed_tool_call_attempt\", \"fallback_agent\")\n",
    "workflow.add_edge(\"fallback_agent\", \"tools\")\n",
    "\n",
    "app = workflow.compile()\n",
    "display(Image(app.get_graph().draw_mermaid_png()))\n"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-11-05T06:02:50.972116Z",
     "start_time": "2024-11-05T06:02:49.121732Z"
    }
   },
   "id": "9d02a16448d212bb",
   "execution_count": 3
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_cef6c8e908444c9fa4ed55', 'function': {'arguments': '{\"request\": {\"topic\": [\"water\", \"flow\", \"serenity\"]}}}', 'name': 'master_haiku_generator'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'model_name': 'qwen-max'}, id='run-a9db479e-dd53-4cee-abc2-e503154e1cda-0', invalid_tool_calls=[{'name': 'master_haiku_generator', 'args': '{\"request\": {\"topic\": [\"water\", \"flow\", \"serenity\"]}}}', 'id': 'call_cef6c8e908444c9fa4ed55', 'error': None, 'type': 'invalid_tool_call'}])]}}\n"
     ]
    }
   ],
   "source": [
    "stream = app.stream(\n",
    "    {\"messages\": [(\"human\", \"Write me an incredible haiku about water.\")]},\n",
    "    {\"recursion_limit\": 10},\n",
    ")\n",
    "\n",
    "for chunk in stream:\n",
    "    print(chunk)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-11-05T06:03:05.268592Z",
     "start_time": "2024-11-05T06:03:02.369817Z"
    }
   },
   "id": "81b074ba19e085ca",
   "execution_count": 4
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
